﻿using System;

namespace Effects.Effects.Tools
{
    /// <summary>
    ///  Delay line with variable delay time according to LFO.
    /// </summary>
    /// <remarks>
    ///   Delay time of this delay time changes with oscillation of given LFO.
    /// </remarks>
    public class OscillatingDelayLine : DelayLine
    {
        /// <summary>
        ///   Buffer for storing samples.
        /// </summary>
        protected double[] data;

        /// <summary>
        ///  Lenght of buffer in samples.
        /// </summary>
        protected int length;

        /// <summary>
        ///  Index to the data array
        /// </summary>
        protected int index;

        /// <summary>
        ///  Time interval between adjacent samples.
        /// </summary>
        protected double stepTime;

        /// <summary>
        ///  Feedback coef.
        /// </summary>
        protected double feedback;

        /// <summary>
        ///  1 - feedback
        /// </summary>
        protected double feedback_1;

        /// <summary>
        ///  Gets or sets feddback parameter.
        /// </summary>
        public double Feedback
        {
            get { return feedback; }
            set
            {
                if (value < -1 || value > 1)
                    throw new ArgumentException();

                this.feedback = value;
                //this.feedback_1 = 1 - value;
            }
        }


        /// <summary>
        ///  Oscillator that controls delay time.
        /// </summary>
        protected Oscillator oscillator;

        /// <summary>
        ///  Gets or sets the oscillator that controlls delay time;
        /// </summary>
        public Oscillator Oscillator
        {
            get { return oscillator; }
            set { oscillator = value; }
        }

        /// <summary>
        ///  Creates VariableDelayLine with given length, feedback parameter and LFO
        /// </summary>
        /// <param name="length"> Length od delay line in samples. It is the maximum delay length. </param>
        /// <param name="stepTime"> Time interval between two adjacent samples </param>
        /// <param name="osc"> LFO that controlls delay time </param>
        /// <param name="feedback"> Feedback parameter from range 0-1 </param>
        public OscillatingDelayLine(int length,double stepTime, Oscillator osc, double feedback)
        {
            if (feedback < 0 || feedback > 1)
                throw new ArgumentException("Feedback must be in range 0-1");

            this.feedback = feedback;
            this.feedback_1 = 1 - feedback;

            this.oscillator = osc;

            this.length = length;
            this.stepTime = stepTime;
            this.index = 0;
            this.data = new double[length];
        }

        /// <summary>
        ///  Creates VariableDelayLine with length corresponding to given delayTime and sampleRate/
        /// </summary>
        /// <param name="delayTime"> Wanted maximal delay time in miliseconds </param>
        /// <param name="sampleRate"> Sample rate of incoming samples </param>
        /// <param name="osc"> LFO that controls delay time </param>
        /// <param name="feedback"> Feedback parameter from range 0-1 </param>
        public OscillatingDelayLine(double delayTime, int sampleRate, Oscillator osc, double feedback)
        {
            if (feedback < 0 || feedback > 1)
                throw new ArgumentException("Feedback must be in range 0-1");

            this.feedback = feedback;
            this.feedback_1 = 1 - feedback;

            this.oscillator = osc;
            this.stepTime = (double)1000 / sampleRate;

            this.length = Convert.ToInt32(delayTime * sampleRate / (double)1000); 
            this.index = 0;
            this.data = new double[length];
        }

        /// <summary>
        ///  Parameter-less constructor for derived classes.
        /// </summary>
        protected OscillatingDelayLine()
        { }


        /// <summary>
        ///  Help member that stores last output of delay line. Used when filtering output data.
        /// </summary>
        protected double lastOut;

        /// <summary>
        ///  Step method implementation
        /// </summary>
        /// <remarks>
        ///   Input signal is sampled at discrete points in time, but oscillation of the LFO can point somwhere in the middle of this points.
        ///   We can't just take closer of the samples a use it, it would produce hissing noise in th output. Instead of that we recreate value using linear interpolation.
        /// </remarks>
        /// <param name="sample"> Sample to push into the delay line.  </param>
        /// <returns> Output of the delay line </returns>
        public override double Step(double sample)
        {
            double delayTime = 1 + (this.length - 3) *  Math.Abs(oscillator.Step(stepTime));
            int delayTimeIndex = Convert.ToInt32(Math.Floor(delayTime));
            double delayTimeIndexPart = delayTime - delayTimeIndex;
            
            double ret;
            if (delayTimeIndexPart != 0)
            {

                // simple linear interpolation
                //ret = delayTimeIndexPart * data[(index + delayTimeIndex) % length];
                //ret += (1 - delayTimeIndexPart) * data[(index + delayTimeIndex + 1) % length];


                // allpass first order filter interpolation
                //ret = delayTimeIndexPart * data[(index + delayTimeIndex) % length];
                //ret += data[(index + delayTimeIndex + 1) % length];
                //ret -= delayTimeIndexPart * lastOut;
                //lastOut = ret; 


                
                // compute interpolation right-floor
                int index0 = (index + delayTimeIndex);
                int index_1=index0-1;
                int index1=index0 + 1 ;
                int index2=index0 + 2 ;

                index0 %= length;
                index_1 %= length;
                index1 %= length;
                index2 %= length;



                // better interpolation
               // get neighbourgh samples
                double y_1= data [index_1];
                double y0 = data [index0];
                double y1 = data [index1];
                double y2 = data [index2];
                
                
                // compute interpolation x
                
                // calculate
                //double c0 = y0;
                //double c1 = 0.5f*(y1-y_1);
                //double c2 = y_1 - 2.5f*y0 + 2.0f*y1 - 0.5f*y2;
                //double c3 = 0.5f*(y2-y_1) + 1.5f*(y0-y1);

                double c0 = y0;
                double c2 = 0.5 * (y_1 + y1) - y0;
                //double c3 = (y2 - 5 * y0 + y_1 + 2 * y1) / 6;
                double c3 = (y2 - y_1 - 3 * y1 + 3 * y0) / 6;
                double c1 = y1 - y0 - c3 - c2;

                ret = ((c3 * delayTimeIndexPart + c2) * delayTimeIndexPart + c1) * delayTimeIndexPart +c0; 

                
            }
            else
                ret = data[(index + delayTimeIndex) % length];

            data[index] = feedback * ret + sample;
            //data[index] = feedback * data[index] + feedback_1 * sample;
            //data[index] = feedback * ret + feedback_1 * sample;

            index--;
            if (index < 0)
                index += length;
            return ret;
        }

        /// <summary>
        ///  Overriden Top property. Gets the top of the delay line.
        /// </summary>
        public override double Top
        {
            get 
            {
                double delayTime = this.length * Math.Abs(oscillator.Step(stepTime));
                int delayTimeIndex = Convert.ToInt32(Math.Floor(delayTime));
                double delayTimeIndexPart = delayTime - delayTimeIndex;

                double ret = delayTimeIndexPart * data[(index + delayTimeIndex) % length];
                ret += (1 - delayTimeIndexPart) * data[(index + delayTimeIndex + 1) % length];

                return ret;
            }
        }

    }
    
}